/**
 *
 */
package jp.seraph.same.view.gui;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.List;

import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import org.apache.commons.collections.Predicate;

import jp.seraph.jsade.model.AngleVelocityCalculator;
import jp.seraph.jsade.model.DefaultAngleVelocityCalculator;
import jp.seraph.jsade.model.JointIdentifierContainer;
import jp.seraph.jsmf.motion.EditableMotion;
import jp.seraph.jsmf.motion.EditableMotionManager;
import jp.seraph.jspf.pose.PoseManager;
import jp.seraph.same.controller.AgentController;
import jp.seraph.same.controller.DefaultMotionController;
import jp.seraph.same.controller.DefaultMotionElementController;
import jp.seraph.same.controller.DefaultMotionSessionController;
import jp.seraph.same.controller.MotionController;
import jp.seraph.same.controller.MotionElementController;
import jp.seraph.same.controller.MotionManagerController;
import jp.seraph.same.controller.MotionSessionController;
import jp.seraph.same.message.MessageUtil;
import jp.seraph.same.model.ChildAddedListener;
import jp.seraph.same.model.ChildRemovedListener;
import jp.seraph.same.model.DefaultMotionModel;
import jp.seraph.same.model.HierarchicalViewModel;
import jp.seraph.same.model.ListChildAddedListener;
import jp.seraph.same.model.ListChildRemovedListener;
import jp.seraph.same.model.ListedHierarchicalViewModel;
import jp.seraph.same.model.ModelRenamedEventListener;
import jp.seraph.same.model.MotionElementModel;
import jp.seraph.same.model.MotionManagerModel;
import jp.seraph.same.model.MotionModel;
import jp.seraph.same.model.MotionSessionModel;
import jp.seraph.same.model.ViewModel;
import jp.seraph.same.view.gui.tree.ElementObject;
import jp.seraph.same.view.gui.tree.ManagerObject;
import jp.seraph.same.view.gui.tree.MotionObject;
import jp.seraph.same.view.gui.tree.SessionObject;
import jp.seraph.same.view.gui.tree.TreeUserObject;
import jp.seraph.same.view.gui.tree.TreeUtil;

/**
 * Motionの表示・編集の基礎となるパネル TODO メニュー文字列の外部化すること
 */
public class MotionExplorer extends JPanel {
    public MotionExplorer(MotionManagerModel aMotionManager, MotionManagerController aMotionManagerController, AgentController aAgentController, PoseManager aPoseManager, JointIdentifierContainer aJointIdContainer) {
        mAgentController = aAgentController;
        mPoseManager = aPoseManager;
        mDefaultCaluculator = new DefaultAngleVelocityCalculator();
        mJointIdContainer = aJointIdContainer;

        initComponent(aMotionManager, aMotionManagerController);
    }

    /**
     *
     */
    private static final long serialVersionUID = 1L;

    private JTree mTree;

    private TreeModel mTreeModel;

    private AgentController mAgentController;

    private PoseManager mPoseManager;

    private AngleVelocityCalculator mDefaultCaluculator;

    private JointIdentifierContainer mJointIdContainer;

    private void initComponent(MotionManagerModel aMotionManager, MotionManagerController aMotionManagerController) {
        TreeModel tModel = initTreeModel(aMotionManager, aMotionManagerController);
        JTree tTree = new JTree(tModel);

        this.setLayout(new BorderLayout());
        this.add(tTree);

        mTree = tTree;
        mTreeModel = tModel;
    }

    private TreeModel initTreeModel(MotionManagerModel aMotionManager, MotionManagerController aMotionManagerController) {
        MotionManagerModel tMotionManager = aMotionManager;
        final MotionManagerController tMotionManagerController = aMotionManagerController;

        final MutableTreeNode tRootNode = new DefaultMutableTreeNode(new ManagerObject(tMotionManager, tMotionManagerController));

        tMotionManager.addAddedListenner(new ChildAddedListener<MotionModel>() {
            public void handle(HierarchicalViewModel<MotionModel> aParent, MotionModel aChild) {
                MotionController tController = new DefaultMotionController(aChild, tMotionManagerController);
                tRootNode.insert(initMotionNode(aChild, tController), tRootNode.getChildCount());
            }
        });

        tMotionManager.addRemovedListener(new ChildRemovedListener<MotionModel>() {
            public void handle(HierarchicalViewModel<MotionModel> aParent, MotionModel aChild) {
                MotionModel tRemovedChild = aChild;
                MutableTreeNode tTarget = findChildNode(tRootNode, tRemovedChild.getName());

                if (tTarget != null)
                    tRootNode.remove(tTarget);
            }
        });

        tMotionManager.addRenamedListener(new ModelRenamedEventListener() {
            public void handle(ViewModel aSender, String aOldName, String aNewName) {
                mTree.repaint();
            }
        });

        for (MotionModel tModel : tMotionManager) {
            MotionController tController = new DefaultMotionController(tModel, tMotionManagerController);
            tRootNode.insert(initMotionNode(tModel, tController), tRootNode.getChildCount());
        }

        return new DefaultTreeModel(tRootNode);
    }

    private MutableTreeNode initMotionNode(MotionModel aMotion, MotionController aController) {
        MotionModel tMotion = aMotion;
        final MotionController tController = aController;

        MotionObject tMotionObject = new MotionObject(tMotion, tController);
        final MutableTreeNode tNode = new DefaultMutableTreeNode(tMotionObject);

        tMotion.addAddedListener(new ListChildAddedListener<MotionSessionModel>(){
            public void handle(ListedHierarchicalViewModel<MotionSessionModel> aSender, int aIndex, MotionSessionModel aNewChild) {
                MotionSessionModel tNewModel = aNewChild;
                MotionSessionController tSessionController = new DefaultMotionSessionController(tNewModel, tController, mPoseManager, mJointIdContainer, mDefaultCaluculator);
                tNode.insert(initSessionNode(tNewModel, tSessionController), aIndex);
            }
        });

        tMotion.addRemovedListener(new ListChildRemovedListener<MotionSessionModel>(){
            public void handle(ListedHierarchicalViewModel<MotionSessionModel> aSender, int aIndex, MotionSessionModel aRemovedChild) {
                tNode.remove(aIndex);
            }
        });

        tMotion.addRenamedListener(new ModelRenamedEventListener() {
            public void handle(ViewModel aSender, String aOldName, String aNewName) {
                mTree.repaint();
            }
        });

        int tSize = tMotion.getChildCount();
        for (int i = 0; i < tSize; i++) {
            MotionSessionModel tSession = tMotion.get(i);
            MotionSessionController tSessionController = new DefaultMotionSessionController(tSession, tController, mPoseManager, mJointIdContainer, mDefaultCaluculator);

            tNode.insert(initSessionNode(tSession, tSessionController), tNode.getChildCount());
        }

        return tNode;
    }

    private MutableTreeNode initSessionNode(MotionSessionModel aSession, MotionSessionController aController) {
        MotionSessionModel tSession = aSession;
        final MotionSessionController tController = aController;

        SessionObject tSessionObject = new SessionObject(tSession, tController);
        final MutableTreeNode tNode = new DefaultMutableTreeNode(tSessionObject);

        tSession.addAddedListenner(new ChildAddedListener<MotionElementModel>(){
            public void handle(HierarchicalViewModel<MotionElementModel> aParent, MotionElementModel aChild) {
                MotionElementModel tNewChild = aChild;
                MotionElementController tNewController = new DefaultMotionElementController(tNewChild, tController, mJointIdContainer);

                tNode.insert(initElementNode(tNewChild, tNewController), tNode.getChildCount());
            }
        });

        tSession.addRemovedListener(new ChildRemovedListener<MotionElementModel>(){
            public void handle(HierarchicalViewModel<MotionElementModel> aParent, MotionElementModel aChild) {
                MotionElementModel tRemovedChild = aChild;
                MutableTreeNode tRemoveNode = findChildNode(tNode, tRemovedChild.getName());

                if(tRemoveNode != null)
                    tNode.remove(tRemoveNode);
            }
        });

        tSession.addRenamedListener(new ModelRenamedEventListener(){
            public void handle(ViewModel aSender, String aOldName, String aNewName) {
                mTree.repaint();
            }
        });

        for (MotionElementModel tElement : tSession) {
            MotionElementController tElementController = new DefaultMotionElementController(tElement, tController, mJointIdContainer);
            tNode.insert(initElementNode(tElement, tElementController), tNode.getChildCount());
        }

        return tNode;
    }

    private MutableTreeNode initElementNode(MotionElementModel aElement, MotionElementController aController) {
        MotionElementModel tElement = aElement;
        MotionElementController tController = aController;

        MutableTreeNode tNode = new DefaultMutableTreeNode(new ElementObject(tElement, tController));

        return tNode;
    }

    private void showPopup(int aX, int aY) {
        int tX = aX;
        int tY = aY;
        TreePath tPath = mTree.getPathForLocation(tX, tY);
        if (tPath == null)
            return;
        DefaultMutableTreeNode tNode = (DefaultMutableTreeNode) tPath.getLastPathComponent();
        Object tUserObject = tNode.getUserObject();

        if (tUserObject instanceof ManagerObject) {
            showPopup(tX, tY,  (ManagerObject) tUserObject);
        } else if (tUserObject instanceof MotionObject) {
            showPopup(tX, tY,  (MotionObject) tUserObject);
        } else if (tUserObject instanceof SessionObject) {
            showPopup(tX, tY,  (SessionObject) tUserObject);
        } else if (tUserObject instanceof ElementObject) {
            showPopup(tX, tY,  (ElementObject) tUserObject);
        } else {
            throw new RuntimeException(MessageUtil.illegalUserObject(tUserObject));
        }
    }

    private void showPopup(int aX, int aY, ManagerObject aObject) {
        int tX = aX;
        int tY = aY;
        final ManagerObject tObject = aObject;
        JPopupMenu tMenu = new JPopupMenu("MotionManager Menu");
        JMenuItem tAddItem = new JMenuItem("AddMotion", KeyEvent.VK_A);
        tAddItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent aE) {
                addMotion(tObject);
            }
        });

        tMenu.add(tAddItem);
        tMenu.show(this, tX, tY);
    }

    private void addMotion(ManagerObject aObject) {
        ManagerObject tObject = aObject;
        MotionManagerModel tManager = tObject.getManager();
        MotionManagerController tController = tObject.getController();

        String tMotionName = "NewMotion";
        while (tManager.contains(tMotionName)) {
            tMotionName += "_";
        }

        tController.createMotion(tMotionName);
    }

    private void showPopup(int aX, int aY, MotionObject aObject) {
        int tX = aX;
        int tY = aY;
        final MotionObject tObject = aObject;
        JPopupMenu tMenu = new JPopupMenu("Motion Menu");
        JMenuItem tAddItem = new JMenuItem("AddSession", KeyEvent.VK_A);
        JMenuItem tRemoveItem = new JMenuItem("Remove", KeyEvent.VK_R);
        JMenuItem tRenameItem = new JMenuItem("Rename", KeyEvent.VK_N);

        tAddItem.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent aE) {
                addSession(tObject);
            }
        });

        // TODO ここまで書いた。
        // イベント残り追加すること

        tMenu.add(tAddItem);
        tMenu.add(tRemoveItem);
        tMenu.add(tRenameItem);
        tMenu.show(this, tX, tY);
    }

    private void addSession(MotionObject aObject){
        MotionObject tObject = aObject;
        MotionModel tMotion = tObject.getMotion();
        MotionController tController = tObject.getController();

        String tSessionName = "NewSession";
        while(tMotion.contains(tSessionName)){
            tSessionName += "_";
        }

        tController.createSession(tMotion.getChildCount(), tSessionName);
    }

    /**
     * TODO この辺実装中
     *
     * @param aObject
     * @param aParent
     */
    private void addSession(MotionObject aObject, MutableTreeNode aParent) {

    }

    private void removeMotion(MotionObject aObject, MutableTreeNode aTarget) {
        MotionController tController = aObject.getController();
        tController.delete();
    }

    private void renameSession(MotionObject aObject, MutableTreeNode aTarget) {

    }

    private void showPopup(int aX, int aY, SessionObject aObject) {
        int tX = aX;
        int tY = aY;
        JPopupMenu tMenu = new JPopupMenu("Motion Menu");
        JMenuItem tAddItem = new JMenuItem("AddElement", KeyEvent.VK_A);
        JMenuItem tRemoveItem = new JMenuItem("Remove", KeyEvent.VK_R);
        JMenuItem tRenameItem = new JMenuItem("Rename", KeyEvent.VK_N);
        JMenuItem tUpItem = new JMenuItem("Up", KeyEvent.VK_U);
        JMenuItem tDownItem = new JMenuItem("Down", KeyEvent.VK_D);

        // TODO イベントをセットすること

        tMenu.add(tAddItem);
        tMenu.add(tRemoveItem);
        tMenu.add(tRenameItem);
        tMenu.add(tUpItem);
        tMenu.add(tDownItem);
        tMenu.show(this, tX, tY);
    }

    private void showPopup(int aX, int aY, ElementObject aObject) {
        int tX = aX;
        int tY = aY;
        JPopupMenu tMenu = new JPopupMenu("Motion Menu");
        JMenuItem tRemoveItem = new JMenuItem("Remove", KeyEvent.VK_R);
        JMenuItem tEditItem = new JMenuItem("Edit", KeyEvent.VK_E);

        // TODO イベントをセットすること

        tMenu.add(tRemoveItem);
        tMenu.add(tEditItem);
        tMenu.show(this, tX, tY);
    }

    private MutableTreeNode findChildNode(MutableTreeNode aParent, String aChildName) {
        final String tTargetName = aChildName;
        final MutableTreeNode tParent = aParent;
        MutableTreeNode tResult = TreeUtil.findNode(tParent, new Predicate() {
            public boolean evaluate(Object aObject) {
                if (!(aObject instanceof DefaultMutableTreeNode))
                    return false;

                DefaultMutableTreeNode tNode = (DefaultMutableTreeNode) aObject;
                TreeUserObject tObject = (TreeUserObject) tNode.getUserObject();
                if (tObject.getModelName().equals(tTargetName))
                    return true;
                else
                    return false;
            }
        });

        return tResult;
    }
}
